using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
#if NETFX_CORE
using Windows.UI.Core;
#else
using System.Windows.Threading;
#endif

namespace FreshMeat.Xaml
{
   public class ErrorNotifier
   {
      readonly object sender;
#if NETFX_CORE
      readonly CoreDispatcher dispatcher;
#else
      readonly Dispatcher dispatcher;
#endif
      readonly Func<EventHandler<DataErrorsChangedEventArgs>> getHandler;
      readonly Action<string> notifyPropertyChanged;
      readonly Dictionary<string, List<string>> dataErrors = new Dictionary<string, List<string>>();

#if NETFX_CORE
      public ErrorNotifier(object sender, CoreDispatcher dispatcher, Func<EventHandler<DataErrorsChangedEventArgs>> getHandler, Action<string> notifyPropertyChanged)
#else
      public ErrorNotifier(object sender, Dispatcher dispatcher, Func<EventHandler<DataErrorsChangedEventArgs>> getHandler, Action<string> notifyPropertyChanged)
#endif
      {
         this.sender = sender;
         this.dispatcher = dispatcher;
         this.getHandler = getHandler;
         this.notifyPropertyChanged = notifyPropertyChanged;
      }

      public void Clear<T>(params Expression<Func<T>>[] propertyExpressions)
      {
         Clear(PropertyAccessor.GetNames(propertyExpressions));
      }

      public void Clear(IEnumerable<string> propertyNames)
      {
         foreach (var name in propertyNames)
            Clear(name);
      }

      public void Clear(string propertyName)
      {
         if (propertyName == null)
            propertyName = "";

         List<string> errors;
         if (dataErrors.TryGetValue(propertyName, out errors))
         {
            if (errors.Count > 0)
            {
               errors.Clear();
               Notify(propertyName);
            }
         }
      }

      public void ClearAll()
      {
         dataErrors.Clear();
         Notify("");
      }

      public void Add<T>(Expression<Func<T>> propertyExpression, string message)
      {
         var name = PropertyAccessor.GetName(propertyExpression);
         Add(name, message);
      }

      protected void Add(string propertyName, string message)
      {
         if (propertyName == null)
            propertyName = "";

         List<string> errors;
         if (!dataErrors.TryGetValue(propertyName, out errors))
         {
            errors = new List<string>();
            dataErrors.Add(propertyName, errors);
         }

         errors.Add(message);
         Notify(propertyName);
      }

      void Notify(string propertyName)
      {
#if NETFX_CORE
         if (dispatcher.HasThreadAccess)
#else
         if (dispatcher.CheckAccess())
#endif
         {
            var handler = getHandler();
            if (handler != null)
               handler(sender, new DataErrorsChangedEventArgs(propertyName));
            notifyPropertyChanged("HasErrors");
         }
         else
         {
            Post(() => Notify(propertyName));
         }
      }

#if NETFX_CORE
      async void Post(Action action)
#else
      void Post(Action action)
#endif
      {
#if NETFX_CORE
         await dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(action));
#else
         dispatcher.BeginInvoke(action);
#endif
      }

      public bool IsValid<T>(Expression<Func<T>> propertyExpression)
      {
         return InternalGetErrors(PropertyAccessor.GetName<T>(propertyExpression)).Count == 0;
      }

      public IEnumerable GetErrors(string propertyName)
      {
         return InternalGetErrors(propertyName);
      }

      List<string> InternalGetErrors(string propertyName)
      {
         if (propertyName == null)
            propertyName = "";

         List<string> errors;
         if (dataErrors.TryGetValue(propertyName, out errors))
            return errors;

         return new List<string>();
      }

      public bool HasErrors
      {
         get { return dataErrors.Values.Any(list => list.Count > 0); }
      }
   }
}